#include "server.h"
#include "client.h"
#include "crypto.h"
#include <string>

std::vector<std::string> pending_trxs;
void  show_pending_transactions()
{
	std::cout  <<  std::string(20, '*') <<  std::endl;
	for(const  auto& trx : pending_trxs)
		std::cout << trx <<  std::endl;
	std::cout  <<  std::string(20, '*') <<  std::endl;
}

void show_wallets(const Server& server) {
	std::cout << std::string(20, '*') << std::endl;
	for(const auto& [p , w] : server.clients)
		std::cout << p->get_id() <<  " : "  << w << std::endl;
	std::cout << std::string(20, '*') << std::endl;
}

Server::Server () {
	clients = std::map<std::shared_ptr<Client>,double>();
}

double Server::get_wallet(std::string id) const {
	for (auto [p , w] : clients) 
		if( p->get_id() ==  id)
			return w;
	return 0.f;
}

static std::string gen_4dstr() {

	std::random_device rd;
	std::mt19937 gen(rd());
	std::uniform_int_distribution<int> u(0,9999);

	std::stringstream ss;
	ss << std::setw(4) << std::setfill('0') << u(gen);
	return ss.str();
}

std::shared_ptr<Client> Server::get_client(std::string id) const {
	for( auto [ptr , w] : clients )
		if(ptr->get_id() == id)
			return ptr;
	return nullptr;
}

std::shared_ptr<Client> Server::add_client(std::string id) {
	
	std::string temp_id;
	std::shared_ptr<Client> dup;
	
	temp_id = id;
	while ( get_client(temp_id) != nullptr ) {
		temp_id = id + gen_4dstr();
	}
	auto [p , success] = clients.insert( 
		std::make_pair(
			std::make_shared<Client>( temp_id , *this ),
			5.f
		)
	);
	if(!success) 
		throw std::runtime_error("insert client failed");
	return p->first;
}

bool Server::parse_trx(std::string trx, std::string & sender, std::string & receiver, double & value) {
	std::string s ,r ;
	double v;
	
	std::stringstream ss(trx);
	std::getline( ss , s , '-' );
	std::cout << s << std::endl;
	if( ss.fail() || ss.eof())
		throw std::runtime_error("trx parse error");

	std::getline(ss,r,'-');
	std::cout << r << std::endl;
	if( ss.fail() || ss.eof())
		throw std::runtime_error("trx parse error");

	ss >> v;
	if( !ss.eof() && ss.fail() )
		throw std::runtime_error("trx parse error or has remain content in trx");

	sender = s;
	receiver = r;
	value = v;

	return true;
}

bool Server::add_pending_trx(std::string trx, std::string signature) const{
	std::string s,r;
	double v;
	try {
		parse_trx(trx, s, r, v);
	}
	catch ( const std::runtime_error &e) {
		return false;
	}

	// get ptr 
	std::shared_ptr<Client> ptr_s,ptr_r;
	ptr_s = get_client(s);
	ptr_r = get_client(r);

	if( ptr_r == nullptr || ptr_s == nullptr )
		return false;

	double value_s = get_wallet(s);
	double value_r = get_wallet(r);

	if( value_s < v )
		return false;

	if( !crypto::verifySignature(ptr_s->get_publickey(), trx, signature) ) 
		return false;

	pending_trxs.push_back(trx);
	return true;
}

size_t Server::mine() {
	std::string mempool;
	for(const auto & trx : pending_trxs)
		mempool += trx;

	size_t nonce;
	while (true) {
		for( auto &[cptr , wallet] : clients ) {
			nonce = cptr->generate_nonce();
			std::stringstream ss;
			ss << mempool << nonce;
			std::string hash = crypto::sha256(ss.str());
			std::cout << ss.str() + " " + hash << std::endl;
			if( hash.substr(0,10).find("000") != std::string::npos ) {
				wallet += 6.25;

				pending_trxs.erase(
					std::remove_if(
						pending_trxs.begin(),
						pending_trxs.end(),
						[this](auto trx) -> auto{
							std::string s,r;
							double v;
							parse_trx(trx,s ,r,v);
							auto ptr_s = get_client(s);
							auto ptr_r = get_client(r);
							if( ptr_r == nullptr || ptr_s == nullptr ) {
								return false;
							}
							if( ptr_s->get_wallet() < v )
								return false;
							this->clients[ptr_r] +=v;
							this->clients[ptr_s] -=v;
							return true;
						}
					),
					pending_trxs.end()
				);
				return nonce;
			}
		}
	}
	return 0;
}